// Copyright (c) 2011 The Kiwii Authors. All rights reserved.
// Use of this source code is governed by the New BSD License that can be
// found in the LICENSE file. See the AUTHORS file for names of contributors.

#include "net/Networking.hxx"
#include "comm/Exception.hxx"
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>
#include <string>
#include <boost/format.hpp>

using namespace boost;

namespace kiwii {

  using comm::Exception;

  namespace net {


    int createTcpListener(unsigned short port, char const* bindaddr) {
      struct sockaddr_in servaddr;

      std::memset(&servaddr, 0, sizeof(servaddr));
      servaddr.sin_family = AF_INET;
      servaddr.sin_port = htons(port);
      servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

      if (bindaddr != nullptr) {
	int r = inet_pton(AF_INET, bindaddr, &servaddr.sin_addr);
	if (r == 0) {
	  throw Exception((format("Invalid bind address: %s") % bindaddr).str());
	} else if (r < 0) {
	  throw Exception("inet_pton():") << errinfo_errno(errno);
	}
      }

      int listenfd = socket(AF_INET, SOCK_STREAM, 0);
      if (listenfd < 0) {
	throw Exception("socket():") << errinfo_errno(errno);
      }

      std::string failMsg;

      int const on = 1;
      if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
	failMsg = "setsockopt():";
	goto FAIL;
      }

      if (bind(listenfd, (struct sockaddr*)(&servaddr), sizeof(servaddr)) < 0) {
	failMsg = "bind():";
	goto FAIL;
      }

      if (listen(listenfd, 511) < 0) { // the magic 511 constant is from Redis which is again from nginx
	failMsg = "listen():";
	goto FAIL;
      }

      //according to UNP v1's saying, we should set the listen fd to nonblocking
      //to avoid blockage on the accept() call.
      int flags;
      if ((flags = fcntl(listenfd, F_GETFL, 0)) < 0) {
	failMsg = "fcntl():";
	goto FAIL;
      }
      if (fcntl(listenfd, F_SETFL, flags | O_NONBLOCK) < 0) {
	failMsg = "fcntl():";
	goto FAIL;
      }

      return listenfd;

    FAIL:
      close(listenfd);
      throw Exception(failMsg) << errinfo_errno(errno);
    }

    void setNonBlocking(int fd) {
      int flags;

      if ((flags = fcntl(fd, F_GETFL, 0)) != -1)
	if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1)
	  return;

      throw Exception("fcntl():") << errinfo_errno(errno);
    }

    void convertBinaryAddressToString(struct sockaddr const* sa, std::string& addr, unsigned short& port) {
      char buf[128]; //Unix domain is largest
      switch(sa->sa_family) {
      case AF_INET: {
	struct sockaddr_in* sin = (struct sockaddr_in*)sa;
	if (inet_ntop(AF_INET, &sin->sin_addr, buf, sizeof(buf)) == NULL) {
	  throw Exception("inet_ntop():") << errinfo_errno(errno);
	}
	addr = buf;
	port = ntohs(sin->sin_port);
	break;
      }

      default: BOOST_ASSERT(0); //For now, we only support IPv4 address format.
      }
    }


  }
}
